package org.batatasframework.properties;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.batatasframework.expression.ExpressionClause;
import org.batatasframework.properties.beans.InternalProperitesGroup;
import org.batatasframework.properties.beans.InternalPropertiesDefine;
import org.batatasframework.properties.macro.MacroCommand;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.util.Assert;
import org.springframework.util.StringValueResolver;

import cn.bonoon.kernel.util.StringHelper;

/**
 * <pre>
 * 某个功能定义的属性文件，都是一个属性文件的资源，再根据这个资源进行具体的解析。
 * 
 * 这里是把属性文件解析成一行一行的字段串
 * 
 * 功能修改：
 * 1. 支持在一行的后面添加注释，和使用使用转义符对注释符进行转义使用
 * 2. 支持在配置文件里的任意的位置引入另一个或多个配置文件的指令({@link PropertiesResource#COMMAND_IMPORT COMMAND_IMPORT})
 * 3(未实现). 加入宏定义，宏定义的第一个单词必须是"macro"，后面再跟着具体宏指令的名称
 *     宏指令会被单独取出，在进行属性文件的解析前开始处理；如：某个类别的属性定义使用某个类进行解析等指定，可以由宏指令来完成
 * 4. 
 * 
 * 注意：表达式的名是不允许出现空格的，所以指令或宏定义都应该使用两个或以上的单词。
 * </pre>
 * 
 * <pre>
 * 初始化后需要调用{@link #load(String)}方法，才能把整个配置文件加载进来；
 * 在调用load方式之前，可以对宏指令进行定义，默认会使用一些宏指令，也可以自定义宏指令的处理。
 * </pre>
 * @author jackson
 *
 */
public final class PropertiesResource {
	
	/** 注释符 */
	public static final String COMMENT_SYMBOL = "#";
	/** 转义符，如：该地方需要使用{@value #COMMENT_SYMBOL}符号，而不把它当成注释内容 */
	public static final char ESCAPE_SYMBOL = '\\';
	/** 换行符，换行符必须出现在一行的最后面 */
	public static final String WRAP_SYMBOL = " \\";
	/** 需要被截取掉的换行符({@value #WRAP_SYMBOL})的长度 */
	public static final int WRAP_SYMBOL_LENGTH = WRAP_SYMBOL.length();
	/** 或字符（|） */
	public static final char OR_SYMBOL = '|';
	
	/** 在定义的属性文件里引入另一个定义文件，注意：同一个文件可以在不同的地方多次被引用，而且是直接引入到这个位置，没有分组的区分的 */
	public static final String COMMAND_IMPORT = "import into ";
	public static final int COMMAND_IMPORT_LENGTH = COMMAND_IMPORT.length();
	
	public static final String MACRO_WITH = "macro ";
	public static final String STRING_RESOLVER_SYMBOL = "${";

	private final Log logger = LogFactory.getLog(getClass());
	
	private final List<String> lines = new ArrayList<>();
	
	private final StringValueResolver stringValueResolver;
	private final ResourcePatternResolver resourcePatternResolver;
	private final List<MacroCommand> macros = new ArrayList<>();
	
	private final List<PretreatedHandler> pretreatedHandlers = new ArrayList<>();
	
	private String[] skipCommentSymbol;
	
	/** 用于防止相互引用而引起的死循环 */
	private class FileReferenceChecker{
		
		private final Map<String, List<String>> fileReferences = new HashMap<>();

		public String check(String parentFile, File file) throws Exception{

			String filePath = file.getPath();
			//TODO 
			if(null != parentFile){
				List<String> tl = fileReferences.get(filePath);
				if(null != tl && tl.contains(parentFile)){
					
					/*
					 * 这里出现了相互引用的情况
					 * 
					 * 相互引用，会导致死循环
					 */
					String errorMsg = "文件[" + parentFile 
							+ "]与文件[" + filePath 
							+ "]，相互引用，造成死循环！";
					
					logger.error(errorMsg);
					throw new Exception(errorMsg);
				}
				
				List<String> pl = fileReferences.get(parentFile);
				if(null == pl){
					pl = new ArrayList<>();
					fileReferences.put(parentFile, pl);
				}
				pl.add(filePath);
			}
			return null;
		}
	}
	
	/** 需要被跳过的特殊的注解符号 */
	public PropertiesResource setSkipCommentSymbol(String... skipCommentSymbol) {
		this.skipCommentSymbol = skipCommentSymbol;
		return this;
	}
	
	public PropertiesResource(
			ResourcePatternResolver resourcePatternResolver, 
			StringValueResolver stringValueResolver){
		this.resourcePatternResolver = resourcePatternResolver;
		this.stringValueResolver = stringValueResolver;
	}
	
	private class PropertiesLineReader implements AutoCloseable{
		
		BufferedReader reader;
		
		String strLine;
		
		PropertiesLineReader(Resource res) throws IOException{
			InputStream fis = res.getInputStream();
			InputStreamReader isr = new InputStreamReader(fis, "utf-8");
			this.reader = new BufferedReader(isr);
		}
		
		boolean read() throws IOException{
			while((strLine = reader.readLine()) != null){
				strLine = strLine.trim();
				if(strLine.startsWith(COMMENT_SYMBOL)){
					continue;
				}else{

					int commentFromIndex = 0;
					
					if(null != skipCommentSymbol){
						for(String scs : skipCommentSymbol){
							int sli = strLine.lastIndexOf(scs);
							if(sli > 0) sli += strLine.length();
							commentFromIndex = Math.max(commentFromIndex, sli);
						}
					}
					
					strLine = cutComment(strLine, commentFromIndex);
				}
				return true;
			}
			
			return false;
		}

		/** 截取掉一行中注释的部分 */
		private String cutComment(String line, int fromIndex){
			int ci = line.indexOf(COMMENT_SYMBOL, fromIndex);
			if(ci > 0){
				if(line.charAt(ci - 1) == ESCAPE_SYMBOL){
					/*
					 * 去掉转义符号，并且从{@value #COMMENT_SYMBOL}号的后面的字符开始，再进行判断
					 */
					line = line.substring(0, ci - 1) + line.substring(ci);
					return cutComment(line, ci + 1);
				}
				return line.substring(0, ci);
				/*
				 * TODO 这里是框架的东西，一定不能在这里处理下面这种情况
				 */
//				/** 字符串中含有#情况，如： 国家标准 | #(2736元) | 省定标准 | #(4000元) | 市定标准 */
//				if(line.charAt(ci - 2) == OR_SYMBOL || line.charAt(ci - 1) == OR_SYMBOL){
//				  String ss="";	
//	              String[] s=line.split("\\|");
//	              for(String si:s){
//	            	  if(!si.contains(COMMENT_SYMBOL)){
//	            		  ss+=si+OR_SYMBOL;
//	            	  }
//	              } 
//	              return ss.substring(0,ss.length()-1); 
//				}
//				else{
//					return line.substring(0, ci);
//				}
			}
			
			return line;
		}
		
		@Override
		public void close() throws Exception {
			reader.close();
			
			reader = null;
		}
		
		String getValue() throws IOException{
			String value = strLine;
			
			while(value.endsWith(WRAP_SYMBOL)){
				value = value.substring(0, value.length() - WRAP_SYMBOL_LENGTH);
				if(read()){
					value += strLine;
				}else break;
			}

			if(value.indexOf(STRING_RESOLVER_SYMBOL) >= 0){
				
				/*
				 * 这里主要是处理使用了${name}的格式，这种格式可以从spring的配置文件里加载定义的内容
				 */
				value = stringValueResolver.resolveStringValue(value);
			}
			
			//防止重置调用这个方法的时候出现异常
			strLine = value;
			return value;
		}
	}
	
	/**
	 * 
	 * @param parentFile 在这个定义文件里引入了"pathPattern"另外的定义文件
	 * @param pathPattern 路径的格式，可以被解析的路径
	 * @param lines
	 * @param macros
	 * @throws Exception
	 */
	private void internalLoad(String parentFile, 
			String pathPattern, 
			FileReferenceChecker checker, 
			List<String> lines, 
			MacroCommandBuilder macroBuilder) throws Exception{
		
		if(StringHelper.isEmpty(pathPattern)){
			logger.warn("没有定义配置文件！");
			return;
		}
		
		Resource[] rs = resourcePatternResolver.getResources(pathPattern);
		if(logger.isInfoEnabled()){
			logger.info("读取配置文件：" + pathPattern + "，共加载资源数(个):" + rs.length);
		}
		
		for(Resource res : rs){
			String filePath = checker.check(parentFile, res.getFile());
			
			try(PropertiesLineReader reader = new PropertiesLineReader(res)){
				while(reader.read()){
					String text = reader.getValue();

					if(macroBuilder.appendMacro(text)){
						/*
						 * 如果这里定义的是宏指令，则这个指令将预先被解析，不被加入到数据列表里
						 * 
						 * 宏指令在一次解析中是全局的，如果是同一个文档的，宏指令只会被解析一次；
						 * 如果是多个不同的文档定义了同一个宏指令的，则可以指定多个处理器，
						 * 但如果处理器的类型相同的，则只会使用一个
						 */
						continue;
					}
					
					if(text.startsWith(COMMAND_IMPORT)){
						/*
						 * 这里判断一下是否使用了引入文件的指令，如果是，则在这里添加另一个文件的数据进来
						 * 
						 * 这里可能会出现两个文件相互引用的问题，如果这样，那么会导致死循环，内在溢出的问题；
						 * 目前没有处理，
						 */
						internalLoad(filePath, text.substring(COMMAND_IMPORT_LENGTH).trim(), checker, lines, macroBuilder);
						
						continue;
					}
					
					/*
					 * 非指令或宏指令的情况，才加入处理的列表中
					 */
					lines.add(text);
				}
			}
		}
	}
	
	private class MacroCommandBuilder{
		private final List<String> handlers = new ArrayList<>();
		private final List<String> commands = new ArrayList<>();
		
		private boolean appendMacro(String text){
			if(text.startsWith(MACRO_WITH)){
				String mt = text.substring(MACRO_WITH.length());
				commands.add(mt.trim());
				return true;
			}
			return false;
		}
	
	}
	
	/** 注意，调用这个方法会把原来的{@link #lines}i记录清除。 */
	public PropertiesResource load(String path) throws Exception{
		lines.clear();
		
		MacroCommandBuilder macroBuilder = new MacroCommandBuilder();
		internalLoad(null, path, new FileReferenceChecker(), lines, macroBuilder);
		
//		/*
//		 * 在这里需要对宏定义进行处理
//		 */
//		for(String mt : macros){
//			int nameEndIndex = mt.indexOf(' ');
//			String macroName;
//			if(nameEndIndex > 0){
//				//带有参数
//				macroName = mt.substring(0, nameEndIndex);
//			}else{
//				//没有带参数
//				macroName = mt;
//			}
//		}
		return this;
	}
	
	/**
	 * <pre>
	 * 同一个定义的名称，可能会有多个定义；所以这里应该使用组数来返回。
	 * 如果是有多个定义名的，则需要使用{@link #resources(String...)}这个方法，
	 * 则返回的是{@link PropertiesDefines}这个接口的对象；
	 * </pre>
	 */
	public PropertiesDefineCollection resources(String name) throws Exception{
		String tn = PropertiesName.adjust(name);
		List<PropertiesDefineBuilder> builderItems = new ArrayList<>();
		PropertiesDefineBuilder currentDefineBuilder = null;
		
		for(String line : lines){
			if(line.isEmpty()) continue;
			if(PropertiesName.checkName(line, tn)){
				currentDefineBuilder = new PropertiesDefineBuilder(PropertiesName.valueOf(line));
				builderItems.add(currentDefineBuilder);
				
				/*
				 * 如果当前处理的行"line"是顶级节点的，在这里处理完后
				 * 进入第二行的处理。
				 */
				continue;
			}
			
			if(null == currentDefineBuilder){
				currentDefineBuilder = new PropertiesDefineBuilder(PropertiesName.valueOf(tn));
				builderItems.add(currentDefineBuilder);
			}
			
			currentDefineBuilder.internalLines.add(line);
		}
		InternalPropertiesDefineCollection collection=new InternalPropertiesDefineCollection(tn, buildDefine(builderItems));
		return collection; 
	}
	
	public PropertiesDefines resources(String... names) throws Exception{
		Assert.notEmpty(names, "必须给定需要解释的定义！");
		/*
		 * 需要把"[name]"或"name"处理成"[name"的格式再进行计算
		 */
		for(int i = 0; i < names.length; i++){
			names[i] = PropertiesName.adjust(names[i]);
		}
		String defaultName = names[0];
		
		/* 用于缓存相同名称的定义 */
		final Map<String, List<PropertiesDefineBuilder>> builderMap = new HashMap<>();
		
		List<PropertiesDefineBuilder> builderItems = null;
		PropertiesDefineBuilder currentDefineBuilder = null;
		
		for(String line : lines){
			if(PropertiesName.checkName(line, names)){
				PropertiesName defineName = PropertiesName.valueOf(line);
				
				/* 经过解析后的定义名，因为在定义文件里，定义的名称可能带着参数 */
				String strDefineName = defineName.toString();
				builderItems = builderMap.get(strDefineName);
				
				if(null == builderItems){
					builderMap.put(strDefineName, builderItems = new ArrayList<>());
				}
				
				currentDefineBuilder = new PropertiesDefineBuilder(defineName);
				builderItems.add(currentDefineBuilder);
				
				/*
				 * 如果当前处理的行"line"是顶级节点的，在这里处理完后
				 * 进入第二行的处理。
				 */
				continue;
			}
			
			if(null == builderItems){
				/*
				 * 这里是处理第一行，并且第一行没有定义哪一个顶级节点的情况下，
				 * 默认把该节点的数据归为"names"里定义的第一个节点
				 */
				builderMap.put(names[0], builderItems = new ArrayList<>());
				currentDefineBuilder = new PropertiesDefineBuilder(PropertiesName.valueOf(defaultName));
				builderItems.add(currentDefineBuilder);
			}
			
			currentDefineBuilder.internalLines.add(line);
		}
		
		return new PropertiesDefinesImpl(builderMap);
	}
	
	private List<PropertiesDefine> buildDefine(List<PropertiesDefineBuilder> defines) throws Exception{
		List<PropertiesDefine> pds = new ArrayList<>(defines.size());
		for(PropertiesDefineBuilder pd : defines){
			pds.add(pd.build());
		}
		
		return pds;
	}
	
	private final class InternalPropertiesDefineCollection implements PropertiesDefineCollection{

		private final String name;
		
		private final List<PropertiesDefine> defines;
		
		InternalPropertiesDefineCollection(String name, List<PropertiesDefine> defines){
			this.name = name;
			this.defines = defines;
		}
		
		@Override
		public Iterator<PropertiesDefine> iterator() {
			return defines.iterator();
		}

		@Override
		public String getName() {
			return name;
		}
		
	}

	private final class PropertiesDefinesImpl implements PropertiesDefines{
		
		private final Map<String, PropertiesDefineCollection> defines = new HashMap<>();
		
		PropertiesDefinesImpl(Map<String, List<PropertiesDefineBuilder>> blocks) throws Exception{
			for(Entry<String, List<PropertiesDefineBuilder>> ent : blocks.entrySet()){
				PropertiesDefineCollection ipdc = new InternalPropertiesDefineCollection(ent.getKey(), buildDefine(ent.getValue()));
				this.defines.put(ipdc.getName(), ipdc);
			}
		}
		
		@Override
		public PropertiesDefineCollection get(String defineName) {
			return defines.get(defineName);
		}

	}
	
	private class PropertiesDefineBuilder {
		
		List<String> internalLines = new ArrayList<>();
		/** 定义的名称，可能会包含参数，至于这些参数如何使用，则应该由具体处理的方法来决定 */
		final PropertiesName name;
		PropertiesDefineBuilder(PropertiesName name){
			this.name = name;
		}
		
		private PropertiesDefine build() throws Exception{
			InternalPropertiesDefine ipd = new InternalPropertiesDefine(name);

			for(MacroCommand mc : macros){
				ipd = mc.getHandler().beforeDefine(mc, PropertiesResource.this, ipd);
			}
			
			List<InternalProperitesGroup> internalGroups = new ArrayList<>();
			
			//正在解析处理的表达式列表
			List<ExpressionClause> parsingItems = ipd.getExpressions();
			InternalProperitesGroup proGroup = null;
			
			NEXTLINE : for(String line : internalLines){
				
				if(PropertiesName.checkName(line)){
					/* 如果这个字段以"[]"号开始和结束的，则认为是组的定义 */
					PropertiesName groupName = PropertiesName.valueOf(line);
					
					proGroup = new InternalProperitesGroup(groupName);
					for(MacroCommand mc : macros){
						proGroup = mc.getHandler().beforeGroup(mc, PropertiesResource.this, ipd, proGroup);
					}
					
					parsingItems = proGroup.getItems();
					internalGroups.add(proGroup);
					continue;
				}
				
				//下一个
				//注意，这里只处理第一个"="号的位置，后面再出现"="则表示是表达式值的内容
				int keyPoint = line.indexOf('=');
				
				String value = null;
				if(keyPoint == -1){
					/*
					 * 如果没有定义"="号，则表示这个就是一个名字的定义，是允许的
					 */
					keyPoint = line.length();
				}else{
					int valuePoint = keyPoint + 1;
					if(valuePoint < line.length()){
						value = line.substring(valuePoint);
					}
				}
				
				/*
				 * 把  place = china的格式分解成为
				 * name = place
				 * value = china
				 * 两个部分
				 */
				PropertiesName name = new PropertiesName(line, keyPoint);
				
				for(ExpressionClause ec : parsingItems){
					if(ec.parse(name, value)){
						continue NEXTLINE;
					}
				}
				
				/*
				 * TODO 这里没有进行对是否存在相同的定义名进行判断
				 * 由开发者自己在定义文件里处理；因为有些情况是可以有两个或多个相同的定义的
				 */
				ExpressionClause ec = new ExpressionClause(name, value);
				for(MacroCommand mc : macros){
					ec = mc.getHandler().readItem(mc, PropertiesResource.this, ipd, proGroup, ec);
				}
				parsingItems.add(ec);
			}
			
			for(InternalProperitesGroup pg : internalGroups){
				for(MacroCommand mc : macros){
					pg = mc.getHandler().afterGroup(mc, PropertiesResource.this, ipd, pg);
				}
				
				ipd.addGroup(pg);
			}

			for(MacroCommand mc : macros){
				ipd = mc.getHandler().afterDefine(mc, PropertiesResource.this, ipd);
			}
			
			return ipd;
		}
	}

	
	public boolean isEmpty() {
		return lines.isEmpty();
	}

	public int size() {
		return lines.size();
	}

}
